import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.InflaterInputStream;

public class ObjectStorage
{
    private class NeuronCache
    {
        // NOTE: we DO NEED this for loading files containing CADU(s),
        // because in large CADUs, Neurons might have been created
        // long before their input fields and -nodes were added,
        // so that GetDrawObjectByCompoundId() would do too
        // many loop iterations to find a Neuron!

        private HashMap<Long, Neuron> NeuronCache = new HashMap<Long, Neuron>();

        private void AddNeuron(Neuron Neuron)
        {
            // TODO: loading a file does sometimes not work properly if this is
            // enabled, don't know why... there should be only one Neuron with
            // this CompoundId... test with Test.LooksChaotic.ncide
            // ->
            // if (NeuronCache.get(Neuron.GetCompoundId()) != null)
            // return;

            NeuronCache.put(Neuron.GetCompoundId(), Neuron);
        }

        private Neuron GetNeuronByCompoundId(long CompoundId)
        {
            return NeuronCache.get(CompoundId);
        }
    }

    private long FileTypeMagicNumber = 0x529FE8A528F41C38L; // just typed this randomly
    private int FileTypeVersion = 2; // increase if changing file format

    private ArrayList<DrawObject> DrawObjects = new ArrayList<DrawObject>(); // durably contains (references to) ALL objects (like Neurons, Nodes, Lines etc.)

    private ArrayList<String> HormonesAlreadyUpdated = new ArrayList<String>(); // temporary, to speed up the decision if a Hormone needs to be RE-updated

    public DrawObject AddObject(String DrawObjectTypeDescription, Object Start, Object End)
    {
        DrawObject AddedMainObject = null;

        // verify
        if (Start == null)
        {
            System.out.println("error in ObjectStorage.AddObject(): Start object is null!");
            return AddedMainObject;
        }
        if (End == null && DrawObjectTypeDescription.equals("Line"))
        {
            System.out.println("error in ObjectStorage.AddObject(): End object is null!");
            return AddedMainObject;
        }

        if (!(Tools.CheckHeapSize()))
        {
            System.out.println("error in ObjectStorage.AddObject(): not enough heap memory left!");
            return AddedMainObject;
        }

        // begin
        String DrawObjectTypeDescriptionAdapted;

        if (DrawObjectTypeDescription.equals("LearningNeuron"))
            DrawObjectTypeDescriptionAdapted = "Neuron"; // every LearningNeuron is actually a normal Neuron with a special, internal flag set
        else
            DrawObjectTypeDescriptionAdapted = DrawObjectTypeDescription;

        switch (DrawObjectTypeDescriptionAdapted)
        {
        case "CommentField":
            CommentField CommentFieldNew = new CommentField((Point) Start, new Point(400, 400));
            DrawObjects.add(CommentFieldNew);
            ArrayList<DrawObject> CommentFieldNewChildObjects = CommentFieldNew.GenerateChildObjects();
            for (int m = 0; m < CommentFieldNewChildObjects.size(); m++)
                DrawObjects.add(CommentFieldNewChildObjects.get(m));
            AddedMainObject = CommentFieldNew;
            break;
        case "TextField":
            TextField TextFieldNew = new TextField((Point) Start);
            DrawObjects.add(TextFieldNew);
            AddedMainObject = TextFieldNew;
            break;
        case "Line":
            if (End instanceof Neuron) // End is Object, cannot use IsNeuron()
            {
                Neuron.NeuronInputDrawObjects InputFieldDrawObjects = ((Neuron) End).AddInputFieldAndNode();
                DrawObjects.add(InputFieldDrawObjects.GetField());
                DrawObjects.add(InputFieldDrawObjects.GetNode());
                Line LineNew = new Line((DrawObject) Start, (DrawObject) InputFieldDrawObjects.GetNode());
                DrawObjects.add(LineNew);
                AddedMainObject = LineNew;
            }
            else
            {
                Line LineNew = new Line((DrawObject) Start, (DrawObject) End);
                DrawObjects.add(LineNew);
                AddedMainObject = LineNew;
            }
            break;
        case "JavaCADU":
            JavaCADU JavaCADUNew = new JavaCADU((Point) Start);
            DrawObjects.add(JavaCADUNew);

            JavaCADU.JavaCADUParameterSet s = ((JavaCADU.JavaCADUParameterSet) End);

            ArrayList<DrawObject> JavaCADUChildDrawObjects = JavaCADUNew.AddChildDrawObjects(
                s.GetAxonCount(), s.GetDendritesCount(), s.GetAllowedDelaysInTicks(), s.GetLearningNeuronCount()
            );

            for (int m = 0; m < JavaCADUChildDrawObjects.size(); m++)
            {
                DrawObjects.add(JavaCADUChildDrawObjects.get(m));
            }
            break;
        case "Neuron":
            Neuron NeuronNew = new Neuron((Point) Start);
            DrawObjects.add(NeuronNew);

            if (DrawObjectTypeDescription.equals("LearningNeuron"))
                NeuronNew.SetIsLearningNeuron(true);

            NeuronOutputNode NeuronOutputNode = NeuronNew.AddOutputNode();
            DrawObjects.add(NeuronOutputNode);

            // NeuronInputNodes/NeuronInputFields added dynamically (see above at 'case "Line":')
            // *if user draws a Line onto Neuron*

            AddedMainObject = NeuronNew;
            break;
        case "NeuronInputField":
            NeuronInputField NeuronInputFieldNew = new NeuronInputField((Point) Start);
            DrawObjects.add(NeuronInputFieldNew);
            AddedMainObject = NeuronInputFieldNew;
            break;
        case "Node":
            Node NodeNew = new Node((Point) Start);
            DrawObjects.add(NodeNew);
            AddedMainObject = NodeNew;
            break;
        case "SampleAndHold":
            SampleAndHold SampleAndHoldNew = new SampleAndHold((Point) Start);
            DrawObjects.add(SampleAndHoldNew);
            SampleAndHoldInputNode SampleAndHoldInputNode = SampleAndHoldNew.AddInputNode();
            DrawObjects.add(SampleAndHoldInputNode);
            SampleAndHoldOutputNode SampleAndHoldOutputNode = SampleAndHoldNew.AddOutputNode();
            DrawObjects.add(SampleAndHoldOutputNode);
            AddedMainObject = SampleAndHoldNew;
            break;
        case "VisualField":
            VisualField VisualFieldNew = new VisualField((Point) Start);
            DrawObjects.add(VisualFieldNew);
            ArrayList<VisualFieldPixel> VisualFieldPixels = VisualFieldNew.AddVisualFieldPixels();
            for (int m = 0; m < VisualFieldPixels.size(); m++)
                DrawObjects.add(VisualFieldPixels.get(m));
            AddedMainObject = VisualFieldNew;
            break;
        }

        return AddedMainObject;
    }

    public void AddObjectDirectly(DrawObject Object)
    {
        if (!(Object.IsZombie())) // not marked as to be disposed?
        {
            DrawObjects.add(Object);
        }
    }

    public void AfterCollectingAllExcitement()
    {
        for (Iterator<DrawObject> i = DrawObjects.iterator(); i.hasNext();)
        {
            DrawObject o = i.next();
            o.AfterCollectingAllExcitementForNextTick();
        }
    }

    public void DeleteHormoneForAll(String HormoneName)
    {
        for (Iterator<DrawObject> i = DrawObjects.iterator(); i.hasNext();)
        {
            DrawObject o = i.next();
            if (o.IsNeuron())
            {
                ((Neuron) o).DeleteHormone(HormoneName);
            }
        }
    }

    public void DeleteObject(DrawObject ObjectToDelete)
    {
        // ATTENTION: do not call this for many objects to delete at once,
        // randomly removing objects from ArrayList<> is not fast,
        // instead call DeleteObjects()!

        ArrayList<DrawObject> ChildDrawObjects = ObjectToDelete.GetChildDrawObjects();

        // ***************************************************************
        // ATTENTION: does not work recursively (yet)! We assume there are
        // not further ChildDrawObjects within any ChildDrawObject!
        // ***************************************************************

        for (int m = 0; m < ChildDrawObjects.size(); m++)
        {
            ChildDrawObjects.get(m).MarkAsZombie();
            DrawObjects.remove(ChildDrawObjects.get(m));
        }

        ObjectToDelete.MarkAsZombie();

        DrawObjects.remove(ObjectToDelete);

        if (ChildDrawObjects != null)
        {
            ChildDrawObjects.clear(); // make sure gc throws away old ArrayList<DrawObject>
            ChildDrawObjects.trimToSize(); // make sure gc throws away old ArrayList<DrawObject>
            ChildDrawObjects = null; // make sure gc throws away old ArrayList<DrawObject>
        }
    }

    public void DeleteObjects(ArrayList<DrawObject> ObjectsToDelete)
    {
        int ZombieCount = 0;

        for (int i = 0; i < ObjectsToDelete.size(); i++)
        {
            DrawObject ObjectToDelete = ObjectsToDelete.get(i);

            ObjectToDelete.MarkAsZombie();
            ZombieCount++;

            ArrayList<DrawObject> ChildDrawObjects = ObjectToDelete.GetChildDrawObjects();

            // ***************************************************************
            // ATTENTION: does not work recursively (yet)! We assume there are
            // not further ChildDrawObjects within any ChildDrawObject!
            // ***************************************************************

            for (int m = 0; m < ChildDrawObjects.size(); m++)
            {
                ChildDrawObjects.get(m).MarkAsZombie();
                ZombieCount++;
            }
        }

        ArrayList<DrawObject> DrawObjectsNew = new ArrayList<DrawObject>(Math.max(0, DrawObjects.size() - ZombieCount)); // Math.max() IS required (if ObjectsToDelete contains an invalid high count of objects)

        for (int i = 0; i < DrawObjects.size(); i++)
        {
            if (!(DrawObjects.get(i).IsZombie()))
            {
                DrawObjectsNew.add(DrawObjects.get(i));
            }
        }

        // access the (kind of) "global" array...

        if (DrawObjects != null)
        {
            DrawObjects.clear(); // make sure gc throws away old ArrayList<DrawObject>
            DrawObjects.trimToSize(); // make sure gc throws away old ArrayList<DrawObject>
            DrawObjects = null; // make sure gc throws away old ArrayList<DrawObject>
        }

        DrawObjects = DrawObjectsNew;
    }

    public void DeleteUnnecessaryObjects()
    {
        for (int ReDo = 0; ReDo < Integer.MAX_VALUE; ReDo++)
        {
            ArrayList<DrawObject> DrawObjectsNew = new ArrayList<DrawObject>();

            for (int i = 0; i < DrawObjects.size(); i++)
            {
                DrawObject o = DrawObjects.get(i);

                if (!(o.IsZombie()))
                {
                    if (o.IsLine())
                    {
                        DrawObject StartDrawObject = ((Line) o).GetStartDrawObject();
                        DrawObject EndDrawObject = ((Line) o).GetEndDrawObject();

                        // NOTE: we assume that if the reference StartDrawObject
                        // or EndDrawObject still points to a DrawObject whose
                        // reference was removed from DrawObjects, this DrawObject
                        // is still present in memory (because of StartDrawObject
                        // or EndDrawObject). Therefore we must use the IsZombie
                        // flag to mark any DrawObject as being deleted.
                        //
                        // Better don't set StartDrawObject and EndDrawObject to
                        // null or the following would probably not work any more:

                        if (!(StartDrawObject.IsZombie()) && !(EndDrawObject.IsZombie()))
                        {
                            DrawObjectsNew.add(o);
                        }
                    }
                    else
                    {
                        DrawObjectsNew.add(o);
                    }
                }
            }

            if (DrawObjectsNew.size() < DrawObjects.size())
            {
                if (DrawObjects != null)
                {
                    DrawObjects.clear(); // make sure gc throws away old ArrayList<DrawObject>
                    DrawObjects.trimToSize(); // make sure gc throws away old ArrayList<DrawObject>
                    DrawObjects = null; // make sure gc throws away old ArrayList<DrawObject>
                }

                DrawObjects = DrawObjectsNew;
            }
            else
            {
                break;
            }

            if (ReDo > 1000)
            {
                System.out.println("error in ObjectStorage.DeleteUnnecessaryObjects(): there seems to be an endless loop, debug this!");
                break;
            }
        }
    }

    public void Draw(Graphics g, boolean UseHandsomeDrawOrder)
    {
        if (UseHandsomeDrawOrder)
        {
            // draw e.g. VisualFields and its pixels and TextFields at last to appear on top, to cover Lines
            for (Iterator<DrawObject> i = DrawObjects.iterator(); i.hasNext();)
            {
                DrawObject o = i.next();

                if (!(o.IsReducedDrawOrderEnabled()) || (o.GetProperties() & DrawObject.PROPERTY_DONT_DRAW_IN_REDUCED_DRAWING_MODE) == 0L)
                {
                    if (o.IsLine())
                    {
                        o.Draw(g);
                    }
                }
            }
            for (Iterator<DrawObject> i = DrawObjects.iterator(); i.hasNext();)
            {
                DrawObject o = i.next();
                if (!(o.IsReducedDrawOrderEnabled()) || (o.GetProperties() & DrawObject.PROPERTY_DONT_DRAW_IN_REDUCED_DRAWING_MODE) == 0L)
                {
                    if (!(o.IsLine()))
                    {
                        o.Draw(g);
                    }
                }
            }
            for (Iterator<DrawObject> i = DrawObjects.iterator(); i.hasNext();)
            {
                DrawObject o = i.next();
                if (!(o.IsReducedDrawOrderEnabled()) || (o.GetProperties() & DrawObject.PROPERTY_DONT_DRAW_IN_REDUCED_DRAWING_MODE) == 0L)
                {
                    o.DrawFinal(g);
                }
            }
        }
        else
        {
            // use raw draw order (as objects were created/added)
            for (Iterator<DrawObject> i = DrawObjects.iterator(); i.hasNext();)
            {
                DrawObject o = i.next();
                if (!(o.IsReducedDrawOrderEnabled()) || (o.GetProperties() & DrawObject.PROPERTY_DONT_DRAW_IN_REDUCED_DRAWING_MODE) == 0L)
                {
                    o.Draw(g);
                }
            }
            for (Iterator<DrawObject> i = DrawObjects.iterator(); i.hasNext();)
            {
                DrawObject o = i.next();
                if (!(o.IsReducedDrawOrderEnabled()) || (o.GetProperties() & DrawObject.PROPERTY_DONT_DRAW_IN_REDUCED_DRAWING_MODE) == 0L)
                {
                    o.DrawFinal(g);
                }
            }
        }
    }

    public void ExciteLines()
    {
        for (Iterator<DrawObject> i = DrawObjects.iterator(); i.hasNext();)
        {
            DrawObject o = i.next();
            if (o.IsLine())
            {
                DrawObject S = ((Line) o).GetStartDrawObject();
                // if (S.IsThereNoteworthyExcitement()) // excites connected Line also when afterglowing
                if (S.DoesActivate(S.GetExcitementMaxFromExcitementHistory())) // excites connected Lines only when being fully excited ("maximal yellow")
                {
                    o.AddExcitementForNextTick(1.0);

                    DrawObject E = ((Line) o).GetEndDrawObject();
                    if (E.CanBeExcitedByUser()) // don't excite everything or problems e.g. with VisualFieldPixels, which "excite back" other objects via Lines for other VisualField patterns (tested)
                    {
                        E.AddExcitementForNextTick(1.0);
                    }
                }
            }
        }
    }

    public void ExciteMultipleObjects(ArrayList<DrawObject> ObjectsToExcite)
    {
        for (Iterator<DrawObject> i = ObjectsToExcite.iterator(); i.hasNext();)
        {
            DrawObject o = i.next();
            o.AddExcitementForNextTick(1.0);
        }
    }

    public void ExciteTextFields(String TextFieldText)
    {
        // NOTE: there might be MULTIPLE TextFields with the same text!
        // This is intended to allow "connecting" distance SubSystems without
        // using forever many Lines.

        ArrayList<TextField> TextFields = GetTextFieldsByText(TextFieldText);

        for (int i = 0; i < TextFields.size(); i++)
        {
            TextField TextField = TextFields.get(i);

            if (TextField.GetText().equals(TextFieldText)) // just to make sure, Objects.GetTextFieldsByText() should have done this already
            {
                TextField.AddExcitementForNextTick(1.0);

                // set text of a TextField to e.g. "Adrenaline=50%" to "release" the
                // Hormone Adrenaline if being activated - recall any TextField can be
                // activated through Line(s) which end in them, so a programmatic
                // release of Hormones is possible!

                UpdateHormoneStrengthForAll(Neuron.ExtractHormoneName(TextField.GetText()), Neuron.ExtractHormoneStrength(TextField.GetText()));
            }
        }
    }

    public boolean ExistsLineFromTo(DrawObject O1, DrawObject O2)
    {
        for (Iterator<DrawObject> i = DrawObjects.iterator(); i.hasNext();)
        {
            DrawObject o = i.next();
            if (o.IsLine())
            {
                if (((Line) o).GetStartDrawObject() == O1 && ((Line) o).GetEndDrawObject() == O2)
                    return true;
                if (((Line) o).GetStartDrawObject() == O2 && ((Line) o).GetEndDrawObject() == O1)
                    return true;
            }
        }

        return false;
    }

    public ArrayList<DrawObject> GetActivatedObjects()
    {
        ArrayList<DrawObject> ActivatedObjects = new ArrayList<DrawObject>();

        for (Iterator<DrawObject> i = DrawObjects.iterator(); i.hasNext();)
        {
            DrawObject o = i.next();

            // "if (o.IsActivated()) ..." does not work, because ExcitementCurrent is
            // increased and decreased (loss) in the same method, so that ExcitementCurrent
            // does NEVER (durably) reach 1.0 and thus o.IsActivated() is always false here...
            // Think about how to fix this in a "logical" way...

            if (o.DoesActivate(o.GetExcitementMaxFromExcitementHistory()))
            // HACK (excitement history was intended for displaying purposes only, REPURPOSE (and rename) it?)
            {
                ActivatedObjects.add(o);
            }
        }

        return ActivatedObjects;
    }

    public ArrayList<DrawObject> GetActivatedObjects(DrawObject SampleDrawObjectOfDesiredClassType)
    {
        ArrayList<DrawObject> ActivatedObjects = new ArrayList<DrawObject>();

        for (Iterator<DrawObject> i = DrawObjects.iterator(); i.hasNext();)
        {
            DrawObject o = i.next();

            if (o.DoesActivate(o.GetExcitementMaxFromExcitementHistory()))
            // HACK (excitement history was intended for displaying purposes only, REPURPOSE (and rename) it?)
            {
                if (o.getClass().isInstance(SampleDrawObjectOfDesiredClassType))
                {
                    ActivatedObjects.add(o);
                }
            }
        }

        return ActivatedObjects;
    }

    public ArrayList<TextField> GetActivatedTextFields()
    {
        ArrayList<TextField> ActivatedTextFields = new ArrayList<TextField>();

        for (Iterator<DrawObject> i = DrawObjects.iterator(); i.hasNext();)
        {
            DrawObject o = i.next();
            if (o.IsTextField())
            {
                if (o.IsActivated())
                {
                    ActivatedTextFields.add((TextField) o);
                }
            }
        }

        return ActivatedTextFields;
    }

    public DrawObject GetDrawObjectByCompoundId(DrawObject SampleDrawObjectOfDesiredClassType, long CompoundId, ArrayList<DrawObject> DrawObjects)
    {
        // IMPORTANT: this function is mainly called when loading an .ncide file.
        // The purpose is to find e.g. a Neuron already added belonging to any
        // child object of that Neuron. As the Neuron was added usually shortly
        // before its child objects, we should search "in the surrounding" of
        // the child object. When loading an .ncide file, the child object
        // is always the last one in (the passed) DrawObjects.
        // So we should quickly find the parent (e.g. the Neuron) RIGHT BEFORE
        // the last element in DrawObjects. If this assumption does not apply
        // (because this function is used by an other caller), the reverse order
        // doesn't hurt, we'll find the desired object anyway.

        for (int i = DrawObjects.size() - 1; i >= 0; i--)
        {
            DrawObject o = DrawObjects.get(i);
            if (o.GetCompoundId() == CompoundId)
            {
                if (o.getClass().isInstance(SampleDrawObjectOfDesiredClassType))
                {
                    return o;
                }
            }
        }

        return null;
    }

    public int GetDrawObjectCount()
    {
        return DrawObjects.size();
    }

    public ArrayList<DrawObject> GetMultiSelectionObjects()
    {
        ArrayList<DrawObject> MultiSelectionObjects = new ArrayList<DrawObject>();

        for (Iterator<DrawObject> i = DrawObjects.iterator(); i.hasNext();)
        {
            DrawObject o = i.next();
            if (o.IsPartOfMultiSelection())
                MultiSelectionObjects.add(o);
        }

        return MultiSelectionObjects;
    }

    public Rectangle GetMultiSelectionRectangle(ArrayList<DrawObject> MultiSelectionObjects)
    {
        int xMin = Integer.MAX_VALUE;
        int yMin = Integer.MAX_VALUE;
        int xMax = Integer.MIN_VALUE;
        int yMax = Integer.MIN_VALUE;

        for (int i = 0; i < MultiSelectionObjects.size(); i++)
        {
            DrawObject o = MultiSelectionObjects.get(i);

            if (o.IsZombie() ||
                (o instanceof CommentField) ||
                (o instanceof Line) ||
                (o instanceof VisualField) ||
                (o instanceof Selection)) // these objects aren't visible or don't have any usable Size info
            {
                continue;
            }

            if (o.Pos.x < xMin)
                xMin = o.Pos.x;
            if (o.Pos.y < yMin)
                yMin = o.Pos.y;

            if ((o.Pos.x + o.Size.x) > xMax)
                xMax = o.Pos.x + o.Size.x;
            if ((o.Pos.y + o.Size.y) > yMax)
                yMax = o.Pos.y + o.Size.y;
        }

        return new Rectangle(xMin, yMin, (xMax - xMin), (yMax - yMin));
    }

    public ArrayList<DrawObject> GetObjectsContainingHormone(String HormoneName)
    {
        ArrayList<DrawObject> ObjectsContainingHormone = new ArrayList<DrawObject>();

        for (Iterator<DrawObject> i = DrawObjects.iterator(); i.hasNext();)
        {
            DrawObject o = i.next();
            if (o.IsNeuron())
            {
                if (((Neuron) o).GetHormoneStrength(HormoneName) != Double.MAX_VALUE)
                    ObjectsContainingHormone.add(o);
            }
        }

        return ObjectsContainingHormone;
    }

    public ArrayList<DrawObject> GetSelectionObjects(Point SelectionTopLeftCornerPos, Point SelectionSize)
    {
        ArrayList<DrawObject> SelectionObjects = new ArrayList<DrawObject>();

        int STLx = SelectionTopLeftCornerPos.x;
        int STLy = SelectionTopLeftCornerPos.y;
        int SSx = SelectionSize.x;
        int SSy = SelectionSize.y;

        if (SSx < 0)
        {
            STLx = STLx - -SSx;
            SSx = -SSx;
        }
        if (SSy < 0)
        {
            STLy = STLy - -SSy;
            SSy = -SSy;
        }

        for (Iterator<DrawObject> i = DrawObjects.iterator(); i.hasNext();)
        {
            DrawObject o = i.next();
            if (o.IsLine())
            {
                DrawObject S = ((Line) o).GetStartDrawObject();
                DrawObject E = ((Line) o).GetEndDrawObject();

                if (S.Pos.x >= STLx && S.Pos.x + S.Size.x < STLx + SSx && S.Pos.y >= STLy && S.Pos.y + S.Size.y < STLy + SSy && E.Pos.x >= STLx && E.Pos.x + E.Size.x < STLx + SSx && E.Pos.y >= STLy && E.Pos.y + E.Size.y < STLy + SSy)
                {
                    SelectionObjects.add(o); // add Line although it cannot be selected *directly* by user
                }
            }
            else if (o.CanBeSelectedByUser())
            {
                if (o.Pos.x >= STLx && o.Pos.x + o.Size.x < STLx + SSx && o.Pos.y >= STLy && o.Pos.y + o.Size.y < STLy + SSy)
                {
                    SelectionObjects.add(o);
                }
            }
        }

        return SelectionObjects;
    }

    public ArrayList<TextField> GetTextFieldsByText(String TextFieldText)
    {
        ArrayList<TextField> TextFields = new ArrayList<TextField>();

        for (Iterator<DrawObject> i = DrawObjects.iterator(); i.hasNext();)
        {
            DrawObject o = i.next();
            if (o.IsTextField())
                if (((TextField) o).GetText().equals(TextFieldText))
                    TextFields.add((TextField) o);
        }

        return TextFields;
    }

    public boolean IsMultiSelectionExisting()
    {
        int DrawObjectCount = DrawObjects.size();
        for (int i = 0; i < DrawObjectCount; i++)
            if (DrawObjects.get(i).IsPartOfMultiSelection())
                return true;
        return false;
    }

    public boolean IsObjectAtPoint(Point P)
    {
        for (Iterator<DrawObject> i = DrawObjects.iterator(); i.hasNext();)
        {
            DrawObject o = i.next();
            if (o.ContainsPoint(P))
                return true;
        }

        return false;
    }

    public boolean IsObjectStateValid(DrawObject o)
    {
        if (o == null)
            return false;

        if (o.IsZombie())
            return false;

        if (o.IsLine())
        {
            if (((Line) o).GetStartDrawObject() == null)
                return false;
            if (((Line) o).GetEndDrawObject() == null)
                return false;

            if (((Line) o).GetStartDrawObject().IsZombie())
                return false;
            if (((Line) o).GetEndDrawObject().IsZombie())
                return false;
        }

        return true;
    }

    public LoadSaveDataEx LoadAdditionalFromFile(String LoadPath)
    {
        return LoadFromFile(LoadPath, true);
    }

    public LoadSaveDataEx LoadAllFromFile(String LoadPath)
    {
        return LoadFromFile(LoadPath, false);
    }

    private LoadSaveDataEx LoadFromFile(String LoadPath, boolean AddToExistingObjects)
    {
        try (DataInputStream is = new DataInputStream(
            new BufferedInputStream(
                new InflaterInputStream(
                    // https://www.geeksforgeeks.org/compressing-and-decompressing-files-in-java/ + https://www.tutorialspoint.com/how-to-compress-a-file-in-java
                    new FileInputStream(LoadPath)
                )
            )
        )) // https://jenkov.com/tutorials/java-exception-handling/try-with-resources.html
        {
            // verify
            long FileTypeMagicNumberRead = is.readLong();
            if (FileTypeMagicNumberRead != FileTypeMagicNumber)
            {
                is.close();
                return new LoadSaveDataEx(false, "File seems not to have been saved by NCIDE", "", 1.0);
            }

            int FileTypeVersionRead = is.readInt();
            if (FileTypeVersionRead != FileTypeVersion)
            {
                is.close();
                return new LoadSaveDataEx(false, "File version does not match this program's version", "", 1.0);
            }

            // reset
            if (AddToExistingObjects == false)
            {
                // throw away old DrawObjects NOW to free up heap memory,
                // necessary if previously run out of memory (tested)...

                if (DrawObjects != null)
                {
                    DrawObjects.clear(); // make sure gc throws away old ArrayList<DrawObject>
                    DrawObjects.trimToSize(); // make sure gc throws away old ArrayList<DrawObject>
                    DrawObjects = null; // make sure gc throws away old ArrayList<DrawObject>
                }

                System.gc(); // throw away old DrawObjects; not sure if REQUEST to run garbage collector has an effect, but it's not harmful anyway
            }

            // begin reading
            int DrawObjectCount = is.readInt();

            // TODO: the following stuff, maybe unify this somehow,
            // don't copy code all the time...:

            // associates a Neuron with its child DrawObjects:
            NeuronCache NeuronCache = new NeuronCache(); // GetDrawObjectByCompoundId() was too slow if loading CADUs

            Neuron CompoundNeuron = null;
            NeuronInputField CompoundNeuronInputField = null;
            NeuronInputNode CompoundNeuronInputNode = null;
            NeuronOutputNode CompoundNeuronOutputNode = null;

            // associates a JavaCADU with its child DrawObjects:
            JavaCADU CompoundJavaCADU = null;

            // associates a SampleAndHold with its child DrawObjects:
            SampleAndHold CompoundSampleAndHold = null;

            // associates a VisualField with its child DrawObjects:
            VisualField CompoundVisualField = null;
            VisualFieldPixel CompoundVisualFieldPixel = null;

            // temporary DrawObject collection, might be added to the main collection or even become it:
            ArrayList<DrawObject> DrawObjectsNew = new ArrayList<DrawObject>(DrawObjectCount);

            // read all DrawObjects saved in file
            for (int i = 0; i < DrawObjectCount; i++)
            {
                String ClassName = Tools.ReadUTF32Bit(is);

                DrawObject n = null; // new DrawObject to assemble, initialize with dummy data, n.StateFromFile(is) loads actual values

                switch (ClassName)
                {
                case "CommentField":
                    n = new CommentField(new Point(0, 0), new Point(0, 0));
                    n.StateFromFile(is);
                    break;
                case "Line":
                    n = new Line(null, null);

                    int StartDrawObjectIndexInDrawObjects = is.readInt();
                    int EndDrawObjectIndexInDrawObjects = is.readInt();

                    n.StateFromFile(is); // do AFTER is.readInt() (see above)!

                    ((Line) n).SetStartDrawObject(DrawObjectsNew.get(StartDrawObjectIndexInDrawObjects));
                    ((Line) n).SetEndDrawObject(DrawObjectsNew.get(EndDrawObjectIndexInDrawObjects));

                    break;
                case "JavaCADU":
                    n = new JavaCADU(new Point(0, 0));
                    n.StateFromFile(is);
                    CompoundJavaCADU = (JavaCADU) n;
                    break;
                case "JavaCADUInputNode":
                    n = new JavaCADUInputNode(new Point(0, 0));
                    n.StateFromFile(is);
                    if (CompoundJavaCADU != null) // parent object (JavaCADU) ALWAYS right before its nodes, in memory and in file
                    {
                        CompoundJavaCADU.AddInputNode((JavaCADUInputNode) n);
                    }
                    break;
                case "JavaCADULearningNode":
                    n = new JavaCADULearningNode(new Point(0, 0));
                    n.StateFromFile(is);
                    if (CompoundJavaCADU != null) // parent object (JavaCADU) ALWAYS right before its nodes, in memory and in file
                    {
                        CompoundJavaCADU.AddLearningNode((JavaCADULearningNode) n);
                    }
                    break;
                case "JavaCADUOutputNode":
                    n = new JavaCADUOutputNode(new Point(0, 0));
                    n.StateFromFile(is);
                    if (CompoundJavaCADU != null) // parent object (JavaCADU) ALWAYS right before its nodes, in memory and in file
                    {
                        CompoundJavaCADU.AddOutputNode((JavaCADUOutputNode) n);
                    }
                    break;
                case "Neuron":
                    n = new Neuron(new Point(0, 0));
                    n.StateFromFile(is); // loads any former CompoundId, which could be in use by already loaded DrawObjects if an additional file is being loaded as SubSystem
                    NeuronCache.AddNeuron((Neuron) n);
                    break;
                case "NeuronInputField":
                    n = new NeuronInputField(new Point(0, 0));
                    n.StateFromFile(is);
                    CompoundNeuronInputField = (NeuronInputField) n;
                    CompoundNeuron = NeuronCache.GetNeuronByCompoundId(n.GetCompoundId());
                    if (CompoundNeuron == null) // fallback
                        CompoundNeuron = (Neuron) GetDrawObjectByCompoundId(new Neuron(new Point(0, 0)), n.GetCompoundId(), DrawObjectsNew); // use the CompoundId saved in file
                    if (CompoundNeuron != null)
                    {
                        if (CompoundNeuronInputField != null && CompoundNeuronInputNode != null)
                        {
                            CompoundNeuron.AddInputFieldAndNode(CompoundNeuronInputField, CompoundNeuronInputNode);
                            CompoundNeuronInputField = null;
                            CompoundNeuronInputNode = null;
                        }
                    }
                    break;
                case "NeuronInputNode":
                    n = new NeuronInputNode(new Point(0, 0));
                    n.StateFromFile(is);
                    CompoundNeuronInputNode = (NeuronInputNode) n;
                    CompoundNeuron = NeuronCache.GetNeuronByCompoundId(n.GetCompoundId());
                    if (CompoundNeuron == null) // fallback
                        CompoundNeuron = (Neuron) GetDrawObjectByCompoundId(new Neuron(new Point(0, 0)), n.GetCompoundId(), DrawObjectsNew); // use the CompoundId saved in file
                    if (CompoundNeuron != null)
                    {
                        if (CompoundNeuronInputField != null && CompoundNeuronInputNode != null)
                        {
                            CompoundNeuron.AddInputFieldAndNode(CompoundNeuronInputField, CompoundNeuronInputNode);
                            CompoundNeuronInputField = null;
                            CompoundNeuronInputNode = null;
                        }
                    }
                    break;
                case "NeuronOutputNode":
                    n = new NeuronOutputNode(new Point(0, 0));
                    n.StateFromFile(is);
                    CompoundNeuronOutputNode = (NeuronOutputNode) n;
                    CompoundNeuron = NeuronCache.GetNeuronByCompoundId(n.GetCompoundId());
                    if (CompoundNeuron == null) // fallback
                        CompoundNeuron = (Neuron) GetDrawObjectByCompoundId(new Neuron(new Point(0, 0)), n.GetCompoundId(), DrawObjectsNew); // use the CompoundId saved in file
                    if (CompoundNeuron != null)
                        CompoundNeuron.AddOutputNode(CompoundNeuronOutputNode);
                    break;
                case "Node":
                    n = new Node(new Point(0, 0));
                    n.StateFromFile(is);
                    break;
                case "SampleAndHold":
                    n = new SampleAndHold(new Point(0, 0));
                    n.StateFromFile(is);
                    CompoundSampleAndHold = (SampleAndHold) n;
                    break;
                case "SampleAndHoldInputNode":
                    n = new SampleAndHoldInputNode(new Point(0, 0));
                    n.StateFromFile(is);
                    if (CompoundSampleAndHold != null) // parent object (SampleAndHold) ALWAYS right before its input and output nodes, in memory and in file
                    {
                        CompoundSampleAndHold.AddInputNode((SampleAndHoldInputNode) n);
                    }
                    break;
                case "SampleAndHoldOutputNode":
                    n = new SampleAndHoldOutputNode(new Point(0, 0));
                    n.StateFromFile(is);
                    if (CompoundSampleAndHold != null) // parent object (SampleAndHold) ALWAYS right before its input and output nodes, in memory and in file
                    {
                        CompoundSampleAndHold.AddOutputNode((SampleAndHoldOutputNode) n);
                    }
                    break;
                case "TextField":
                    n = new TextField(new Point(0, 0));
                    n.StateFromFile(is);
                    break;
                case "VisualField":
                    n = new VisualField(new Point(0, 0));
                    n.StateFromFile(is);
                    CompoundVisualField = (VisualField) n;
                    break;
                case "VisualFieldPixel":
                    n = new VisualFieldPixel(new Point(0, 0));
                    n.StateFromFile(is);
                    CompoundVisualFieldPixel = (VisualFieldPixel) n;
                    if (CompoundVisualField != null) // parent object (VisualField) ALWAYS right before its pixels, in memory and in file
                    {
                        CompoundVisualField.AddVisualFieldPixel(CompoundVisualFieldPixel);
                    }
                    break;
                }

                if (IsObjectStateValid(n))
                {
                    DrawObjectsNew.add(n); // finally add new DrawObject to temporary collection
                }

                if ((i % 10000) == 0)
                {
                    if (!(Tools.CheckHeapSize()))
                    {
                        // ****************************************************
                        is.close();
                        return new LoadSaveDataEx(false, "not enough memory left (too many objects) - you might not be able to add further objects", "", 1.0);
                        // ****************************************************
                    }
                }
            }

            // read tag string, contains the "IO program" to be worked up when running simulation
            String AdditionalData = Tools.ReadUTF32Bit(is);

            is.close();

            // adapt CompoundIds
            if (AddToExistingObjects)
            {
                // determine greatest CompoundId value currently in use by the
                // FORMERLY loaded file(s) (the following does only make sense
                // when ADDING DrawObjectsNew as SubSystem):

                long CompoundIdLoadedMax = -1;

                for (Iterator<DrawObject> i = DrawObjects.iterator(); i.hasNext();)
                {
                    DrawObject o = i.next();
                    if (o.GetCompoundId() != DrawObject.ID_UNSET)
                        if (o.GetCompoundId() > CompoundIdLoadedMax)
                            CompoundIdLoadedMax = o.GetCompoundId();
                }

                // make sure NEWLY added objects have a greater CompoundId:

                long CompoundIdShift = +(CompoundIdLoadedMax + 1); // add 1!
                long CompoundIdAssignedMax = -1;

                for (Iterator<DrawObject> i = DrawObjectsNew.iterator(); i.hasNext();)
                {
                    DrawObject o = i.next();
                    if (o.GetCompoundId() != DrawObject.ID_UNSET)
                    {
                        long CompoundIdAdapted = o.GetCompoundId() + CompoundIdShift;
                        o.SetCompoundId(CompoundIdAdapted);
                        if (CompoundIdAdapted > CompoundIdAssignedMax)
                            CompoundIdAssignedMax = CompoundIdAdapted;
                    }
                }

                // also make sure newly CREATED (using the 'n' key) DrawObjects have an even greater CompoundId:

                DrawObject.SetMinimalCompoundId(CompoundIdAssignedMax + 1);
            }

            // finally set DrawObjectsNew in use
            if (AddToExistingObjects)
            {
                DrawObjects.addAll(DrawObjectsNew); // add the temporary DrawObjects collection to the main collection
            }
            else
            {
                if (DrawObjects != null)
                {
                    DrawObjects.clear(); // make sure gc throws away old ArrayList<DrawObject>
                    DrawObjects.trimToSize(); // make sure gc throws away old ArrayList<DrawObject>
                    DrawObjects = null; // make sure gc throws away old ArrayList<DrawObject>
                }

                DrawObjects = DrawObjectsNew; // make the temporary DrawObjects collection the main collection of the whole program

                System.gc(); // throw away old DrawObjects; not sure if REQUEST to run garbage collector has an effect, but it's not harmful anyway
            }

            return new LoadSaveDataEx(AdditionalData);
        }
        catch (Exception e)
        {
            System.out.println("error in ObjectStorage.LoadFromFile(): " + e.getMessage());

            if (DrawObjects == null)
            {
                // create any new object collection or the system gets completely screwed up (tested):
                DrawObjects = new ArrayList<DrawObject>();
            }

            return new LoadSaveDataEx(false, e.getMessage(), "", 1.0);
        }
    }

    public void LoseExcitementForAllObjects()
    {
        for (Iterator<DrawObject> i = DrawObjects.iterator(); i.hasNext();)
        {
            DrawObject o = i.next();
            o.LoseExcitementAndAddExcitementForNextTick();
        }
    }

    public void MoveAll(Point MoveAmount)
    {
        for (Iterator<DrawObject> i = DrawObjects.iterator(); i.hasNext();)
        {
            DrawObject o = i.next();
            if (o.CanBeMovedByUser() && !(o.HasParent()))
            {
                o.Move(MoveAmount);
            }
        }
    }

    public void MoveMultiSelection(Point MoveAmount)
    {
        for (Iterator<DrawObject> i = DrawObjects.iterator(); i.hasNext();)
        {
            DrawObject o = i.next();
            if (o.IsPartOfMultiSelection())
            {
                if (o.CanBeMovedByUser() && !(o.HasParent()))
                {
                    o.Move(MoveAmount);
                }
            }
        }
    }

    public void ReplaceHormoneNameForAll(String HormoneNameIn, String HormoneNameOut)
    {
        for (Iterator<DrawObject> i = DrawObjects.iterator(); i.hasNext();)
        {
            DrawObject o = i.next();
            if (o.IsNeuron())
            {
                ((Neuron) o).ReplaceHormoneName(HormoneNameIn, HormoneNameOut);
            }
        }
    }

    public void ResetStateForAllObjects()
    {
        for (Iterator<DrawObject> i = DrawObjects.iterator(); i.hasNext();)
        {
            DrawObject o = i.next();
            o.ResetState();
        }
    }

    public void ResetUpdateHormoneStrengthForAll()
    {
        HormonesAlreadyUpdated.clear();
    }

    public LoadSaveDataEx SaveAllToFile(String SavePath, LoadSaveDataEx LoadSaveDataEx)
    {
        return SaveToFile(SavePath, false, DrawObjects, LoadSaveDataEx);
    }

    public LoadSaveDataEx SaveEmptyFile(String EmptyFilePath)
    { // returns error description or "" in case of no error

        ArrayList<DrawObject> EmptyDrawObjects = new ArrayList<DrawObject>();

        EmptyDrawObjects.clear(); // just to make sure there's nothing in it

        return SaveToFile(EmptyFilePath, false, EmptyDrawObjects, new LoadSaveDataEx(true, "", "", 1.0));
    }

    public LoadSaveDataEx SaveMultiSelectionToFile(String SavePath, LoadSaveDataEx LoadSaveDataEx)
    {
        return SaveToFile(SavePath, true, DrawObjects, LoadSaveDataEx);
    }

    private LoadSaveDataEx SaveToFile(String SavePath, boolean SaveMultiSelectionOnly, ArrayList<DrawObject> DrawObjects, LoadSaveDataEx LoadSaveDataEx)
    {
        try (DataOutputStream os = new DataOutputStream(
            new BufferedOutputStream(
                new DeflaterOutputStream(
                    // ^ bless it, just this one code line reduced 600 MB to 22 MB in test, from https://www.tutorialspoint.com/how-to-compress-a-file-in-java
                    new FileOutputStream(SavePath)
                )
            )
        )) // https://jenkov.com/tutorials/java-exception-handling/try-with-resources.html
        {
            int DrawObjectCount;

            if (SaveMultiSelectionOnly)
            {
                DrawObjectCount = 0;
                for (Iterator<DrawObject> i = DrawObjects.iterator(); i.hasNext();)
                    if (i.next().IsPartOfMultiSelection)
                        DrawObjectCount++;
            }
            else
            {
                DrawObjectCount = DrawObjects.size();
            }

            os.writeLong(FileTypeMagicNumber);
            os.writeInt(FileTypeVersion);
            os.writeInt(DrawObjectCount);

            int DrawObjectsSavedCount = 0;
            HashMap<DrawObject, Integer> DrawObjectsSaved = new HashMap<DrawObject, Integer>();

            for (Iterator<DrawObject> i = DrawObjects.iterator(); i.hasNext();)
            {
                DrawObject o = i.next();
                if (IsObjectStateValid(o))
                {
                    if (!(SaveMultiSelectionOnly) || o.IsPartOfMultiSelection)
                    {
                        String ClassName = o.getClass().getSimpleName();
                        Tools.WriteUTF32Bit(os, ClassName);

                        if (o.IsLine())
                        {
                            // we search, using random access, a DrawObject we've already saved,
                            // to get its index (how many objects have been saved to file before?);
                            // therefore we use a HashMap, which is searched very fast
                            // (looping through DrawObjects was much too slow):

                            int StartDrawObjectIndexInDrawObjects = DrawObjectsSaved.get(((Line) o).GetStartDrawObject()) - 1;
                            int EndDrawObjectIndexInDrawObjects = DrawObjectsSaved.get(((Line) o).GetEndDrawObject()) - 1;

                            os.writeInt(StartDrawObjectIndexInDrawObjects);
                            os.writeInt(EndDrawObjectIndexInDrawObjects);
                        }

                        o.StateToFile(os);

                        DrawObjectsSavedCount++;
                        DrawObjectsSaved.put(o, DrawObjectsSavedCount);
                    }
                }
            }

            Tools.WriteUTF32Bit(os, LoadSaveDataEx.StateToFileString());
            os.close();

            return new LoadSaveDataEx(true, "", "", 1.0);
        }
        catch (Exception e)
        {
            System.out.println("error in ObjectStorage.SaveToFile(): " + e.getMessage());

            return new LoadSaveDataEx(false, e.getMessage(), "", 1.0);
        }
    }

    public void SetPartOfMultiSelection(int DrawObjectIndex)
    {
        if (DrawObjectIndex >= 0 && DrawObjectIndex < DrawObjects.size())
            DrawObjects.get(DrawObjectIndex).SetPartOfMultiSelection(true);
        else
            System.out.println("error in ObjectStorage.SetPartOfMultiSelection(): passed value not useful!");
    }

    public void ToggleNeuronInputFieldByIndex(ArrayList<DrawObject> Objects, int InputFieldIndex)
    {
        for (Iterator<DrawObject> i = Objects.iterator(); i.hasNext();)
        {
            DrawObject o = i.next();
            if (o.IsNeuron())
            {
                ((Neuron) o).ToggleInputFieldByIndex(InputFieldIndex);
            }
        }
    }

    public void TransferActivatedTextFieldExcitement()
    {
        // NOTE: DO NOT USE TextField.AddExcitementForNextTick(), because then
        // TextFields will be activated in the NEXT tick. This leads to another
        // call of this procedure, which would call TextField.AddExcitementForNextTick()
        // AGAIN. As a result, TextFields would stay activated durably (tested).
        // Instead, access the excitement values DIRECTLY - "transfer" the excitement
        // state of one TextField to all others with the same text.

        ArrayList<String> ActivatedTextFieldsText = new ArrayList<String>(); // we don't use NESTED for-all-DrawObjects-loops, of course
        for (Iterator<DrawObject> i = DrawObjects.iterator(); i.hasNext();)
        {
            DrawObject o = i.next();
            if (o.IsTextField()) // additional check (actually surplus, but just to make sure), ObjectStorage.GetActivatedObjects([...]) should have returned TextFields only
            {
                if (o.DoesActivate(o.ExcitementAddForNextTick))
                {
                    String TextFieldText = ((TextField) o).GetText();

                    if (!(ActivatedTextFieldsText.contains(TextFieldText))) // .contains() checks String CONTENT, not reference (tested), so this is effective!
                        ActivatedTextFieldsText.add(TextFieldText);
                }
            }
        }

        // System.out.println("exciting " + ActivatedTextFieldsText.size() + " TextFields");

        for (Iterator<DrawObject> i = DrawObjects.iterator(); i.hasNext();)
        {
            DrawObject o = i.next();
            if (o.IsTextField()) // additional check (actually surplus, but just to make sure), ObjectStorage.GetActivatedObjects([...]) should have returned TextFields only
            {
                for (int t = 0; t < ActivatedTextFieldsText.size(); t++) // loop through this only if we have a TextField
                {
                    if (((TextField) o).GetText().equals(ActivatedTextFieldsText.get(t)))
                    {
                        o.ExcitementAddForNextTick = 1.0;
                    }
                }
            }
        }
    }

    public DrawObject TrySelectDrawObject(Point MousePosPoint)
    {
        // prefer (in z-order) objects the user usually wants to select
        for (int i = DrawObjects.size() - 1; i >= 0; i--)
        {
            // loop in reverse order because when drawing, the DrawObjects with highest
            // index are drawn last, so that they are visible to the user on top -
            // and the user expects to select the object he sees on top

            DrawObject o = DrawObjects.get(i);
            if (!(o.IsLine()))
            {
                if (o.ContainsPoint(MousePosPoint))
                    return o;
            }
        }

        // if nothing clearly selected, check if any Line is near
        for (int i = DrawObjects.size() - 1; i >= 0; i--)
        {
            // loop in reverse order because when drawing, the DrawObjects with highest
            // index are drawn last, so that they are visible to the user on top -
            // and the user expects to select the object he sees on top

            DrawObject o = DrawObjects.get(i);
            if (o.IsLine())
            {
                double Distance = Math.abs(((Line) o).GetDistanceToPoint(MousePosPoint));
                if (Distance < 5)
                {
                    return o;
                }
            }
        }

        return null;
    }

    public DrawObject TrySelectNearObject(Point MousePosPoint)
    {
        double DistanceMin = Double.MAX_VALUE;
        DrawObject DistanceMinDrawObject = null;

        for (int i = DrawObjects.size() - 1; i >= 0; i--)
        {
            // loop in reverse order because when drawing, the DrawObjects with highest
            // index are drawn last, so that they are visible to the user on top -
            // and the user expects to select the object he sees on top

            DrawObject o = DrawObjects.get(i);
            if (o.IsLine())
            {
                double Distance = Math.abs(((Line) o).GetDistanceToPoint(MousePosPoint));
                if (Distance < 25 && Distance < (DistanceMin - 1.0)) // we subtract 1.0 to (effectively) avoid flickering in case there are two or more Lines above each other, especially when the Lines of CommentFields overlap
                {
                    DistanceMin = Distance;
                    DistanceMinDrawObject = o;
                }
            }
            else
            {
                if (o.ContainsPoint(MousePosPoint))
                    return o; // return instantly, it's the best object for sure
            }
        }

        return DistanceMinDrawObject;
    }

    public void UnHighlightAllObjects()
    {
        for (Iterator<DrawObject> i = DrawObjects.iterator(); i.hasNext();)
        {
            DrawObject o = i.next();
            o.SetHighlighted(false);
        }
    }

    public void UnSetPartOfMultiSelectionForAllObjects()
    {
        for (Iterator<DrawObject> i = DrawObjects.iterator(); i.hasNext();)
        {
            DrawObject o = i.next();
            o.SetPartOfMultiSelection(false);
        }
    }

    public void UpdateHormoneStrengthForAll(String HormoneName, double StrengthNew)
    {
        for (int n = 0; n < HormonesAlreadyUpdated.size(); n++) // we loop through (very few) Hormone NAMES, NOT through (possibly very many) DrawObjects using this Hormone name!
            if (HormonesAlreadyUpdated.get(n).equals(HormoneName))
                return; // if multiple StrengthNews for the same Hormone name are passed (what is not useful anyway), all except the first Strength are ignored (this behavior would also show up WITHOUT this speed-up!)

        HormonesAlreadyUpdated.add(HormoneName);

        for (Iterator<DrawObject> i = DrawObjects.iterator(); i.hasNext();)
        {
            DrawObject o = i.next();
            if (o.IsNeuron())
            {
                ((Neuron) o).SetHormoneStrength(HormoneName, StrengthNew);
            }
        }
        return;
    }
}
